home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / loginstat < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  38.2 KB  |  1,113 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) loginstat.gawk 1.3 94/03/09
  4. # 90/11/03 john h. dubois iii (john@armory.com)
  5. # 90/11/15 made compatible with short last,
  6. #       which does not include the "device name" field.
  7. #       Its output is recognized by begining with a header.
  8. # 91/06/30 replaced incorrect continues with nexts
  9. #       Change to not do variable assignments on command line
  10. #       and expect them to be set in BEGIN block
  11. #       (doesn't work in some versions of awk)
  12. # 91/10/18 ignore "last" lines with no user
  13. # 92/02/16 added help option
  14. # 92/03/20 Ported to UNIX:
  15. #          Use Device column instead of Line column because it's nice on
  16. #          UNIX systems where Line is an inittab tag.
  17. #          Set tty name to "ftp" if that's what Line is.
  18. #          If a header is found, check that it isn't from UNIX last.
  19. # 92/04/17 Made into a #!awk script (to big to pass on command line except
  20. #          with tiny environment)
  21. # 92/06/26 Ignore entries created by timed
  22. # 92/08/20 Changed name to loginstat
  23. # 93/06/06 Set Line to uu for uucpd entries, widened fields,
  24. #          use awk qsort instead of piping through sort.
  25. # 94/01/08 Added [ilot] options.  Ignore time on 'shutdown' lines.
  26. # 94/03/09 Use gawk so - options can be given
  27.  
  28. BEGIN {
  29.     Name = "loginstat"
  30.     Usage = "Usage: " Name " [-holt] [-i<user1,...>] [utmp-file]"
  31.     ARGC = Opts(Name,Usage,"holti:",0)
  32.     if ("h" in Options) {
  33.     print \
  34. Name ": print a summary of user & tty login statistics.\n" \
  35. Usage "\n"\
  36. Name " prints a summary of logins by user and tty type.\n" \
  37. "If [utmp-file] is given, it should be a file of utmp records.\n"\
  38. "If no filenames are given, the information in /etc/wtmp is used.\n" \
  39. "Options:\n"\
  40. "-h: Print this help.\n"\
  41. "-l: Sort by number of logins (default).\n"\
  42. "-o: Sort by online time.\n"\
  43. "-t: Sort by time per login (average duration of each login session)\n"\
  44. "    If more than one sort option is given, multiple reports are printed.\n"\
  45. "-i<user1,...>: Ignore the users given in the comma-separated list."
  46.     exit(0)
  47.     }
  48.     if ("i" in Options)
  49.     MakeSet(IgnoreUsers,Options["i"],",")
  50.     if (ARGC < 2) {
  51.     ARGV[1] = "/etc/wtmp"
  52.     ARGC = 2
  53.     }
  54.     Cmd = "last -w " ARGV[1]
  55.     ret = (Cmd | getline)
  56.     if (ret == 1)
  57.     GoodData = 1
  58.     LastAdj = CheckVer()
  59.     for (i = 1; i < ARGC; i++) {
  60.     Cmd = "last -w " ARGV[i]
  61.     ret = (Cmd | getline)
  62.     if (ret == 1)
  63.         GoodData = 1
  64.     while ((Cmd | getline) == 1)
  65.         ProcLine()
  66.     close(Cmd)
  67.     }
  68.     if (GoodData)
  69.     ProcData()
  70.     exit(!GoodData)
  71. }
  72.  
  73. function CheckVer(  LastAdj) {
  74.     LastAdj = 1
  75.     if ($1 == "User") {
  76.     if ($2 != "Line")    # using "short last"
  77.         LastAdj = 0
  78.     }
  79.     else
  80.     ProcLine()
  81.     return LastAdj
  82. }
  83.  
  84. # Process a line of output from the 'last' command.
  85. # Uses global LastAdj.
  86. # Information is stored in the globals:
  87. # start: text version of last time seen.
  88. # end: text version of first time seen.
  89. # LastLogins[user]: Last login times.
  90. # min[user]: Number of minutes of login time.
  91. # tty_min[tty]: Number of minutes each tty type used.
  92. # bytty_logins[user,tty]: Number of logins on each tty type.
  93. # The following are not incremented for login sessions that have a
  94. # 'shutdown' comment:
  95. # logins[user]: Number of logins.
  96. # tty_logins[tty]: Number of logins on each tty type.
  97. function ProcLine(  LoginTime,ElapsedTime,Minutes,HM,tty,Comment) {
  98.     # skip entries with no user name or that were created by timed
  99.     if ($2 ~ "^(tty|date)" || $1 in IgnoreUsers)
  100.     return
  101.     if (LastAdj) {
  102.     LoginTime =  $5 " " $6 " " $7 " " $8
  103.     if (! ($1 in LastLogins))
  104.         LastLogins[$1] = sprintf("%3s %3s %2s",$5,$6,$7)
  105.     }
  106.     else {
  107.     LoginTime =  $4 " " $5 " " $6 " " $7
  108.     if (! ($1 in LastLogins))
  109.         LastLogins[$1] = sprintf("%3s %3s %2s",$4,$5,$6)
  110.     }
  111.     if (end == "")
  112.     end = LoginTime
  113.     ElapsedTime = $(8 + LastAdj)
  114.     if (ElapsedTime ~ "d") {
  115.     Minutes = substr(ElapsedTime,1,length(ElapsedTime) - 1) * 24 * 60
  116.     HM = $(9 + LastAdj)
  117.     Comment = $(10 + LastAdj)
  118.     }
  119.     else {
  120.     Minutes = 0
  121.     HM = ElapsedTime
  122.     Comment = $(9 + LastAdj)
  123.     }
  124.     if (LastAdj) {
  125.     if ($3 ~ "^tty")
  126.         tty = substr($3,4)
  127.     else if ($2 == "ftp")
  128.         tty = "ftp"
  129.     else if ($2 == "uucp")
  130.         tty = "uucp"
  131.     else
  132.         printf "Unknown device type \"%s\" for line %s:\n%s\n",$3,$2,$0 \
  133.         | "cat 1>&2"
  134.     }
  135.     else
  136.     tty = $2
  137.     if ( tty ~ /^[01][0-9]$/ )
  138.     tty = "con"
  139.     else if ( tty ~ /^p/ )
  140.     tty = "pty"
  141.     else if ( tty ~ /[A-Z]/ )
  142.     tty = "mod"
  143.     else if ( tty ~ /[0-9]/ )
  144.     tty = "trm"
  145.     if (Comment != "shutdown") {
  146.     split(HM,time,":")
  147.     Minutes += time[1] * 60 + time[2]
  148.     min[$1] += Minutes
  149.     tty_min[tty] += Minutes
  150.     logins[$1]++
  151.     tty_logins[tty]++
  152.     }
  153.     bytty_logins[tty,$1]++
  154.     start = LoginTime
  155. }
  156.  
  157. function Min2HM(min) {
  158.     if (min >= 60)
  159.     return sprintf("%d:%02d",min / 60,min % 60)
  160.     else
  161.     return sprintf("%d",min)
  162. }
  163.  
  164. # Sets/uses globals nus, TotalLogins, TotMinutes
  165. function ProcData(\
  166. W,TTYList,tty,Format,NumTTYs,user,TimePerLogin,i,T,ReportDone,k) {
  167.     W = 5
  168.     TTYList[NumTTYs = 1] = "con"
  169.     for (tty in tty_min)
  170.     if (tty != "con")
  171.         TTYList[++NumTTYs] = tty
  172.     qsortSegment(TTYList,2,NumTTYs)
  173.     nus = TotalLogins = TotMinutes = 0
  174.     printf "From  %s  to  %s:\n",start,end
  175.     Format = "%-8s %6s %10s %8s"
  176.     for (user in logins)
  177.     TimePerLogin[user] = min[user] / logins[user]
  178.  
  179.     PrintHdr(TTYList,NumTTYs,W,Format)
  180.     if ("o" in Options) {
  181.     PrintUserStats(qsortArbIndByValue(min,k),k,Format,min,TimePerLogin,
  182.     TTYList,W,bytty_logins,NumTTYs)
  183.     ReportDone = 1
  184.     }
  185.     if ("t" in Options) {
  186.     if (ReportDone) {
  187.         print ""
  188.         PrintHdr(TTYList,NumTTYs,W,Format)
  189.         split("",k,"")
  190.     }
  191.     PrintUserStats(qsortArbIndByValue(TimePerLogin,k),k,Format,min,
  192.     TimePerLogin,TTYList,W,bytty_logins,NumTTYs)
  193.     ReportDone = 1
  194.     }
  195.     if ("l" in Options || !ReportDone) {
  196.     if (ReportDone) {
  197.         print ""
  198.         PrintHdr(TTYList,NumTTYs,W,Format)
  199.         split("",k,"")
  200.     }
  201.     PrintUserStats(qsort_arb_ind(logins,k),k,Format,min,
  202.     TimePerLogin,TTYList,W,bytty_logins,NumTTYs)
  203.     }
  204.     PrintTTYtots(Format,TTYList,W,TotalLogins,nus,TotMinutes,NumTTYs,
  205.     tty_logins,tty_min)
  206. }
  207.  
  208. function PrintHdr(TTYList,NumTTYs,W,Format,  tty,T) {
  209.     printf Format,"User","Logins","Time","T/Lgn"
  210.     for (tty = TTYList[T = 1]; T <= NumTTYs; tty = TTYList[++T]) 
  211.     printf "%" W "s",tty
  212.     printf "  Last login\n"
  213. }
  214.  
  215. # Sets globals nus, TotalLogins, TotMinutes
  216. function PrintUserStats(NumEnt,k,Format,min,TimePerLogin,TTYList,W,
  217. bytty_logins,NumTTYs,  tty,user,i,T,LogTot,line) {
  218.     nus = TotalLogins = TotMinutes = 0
  219.     for (i = NumEnt; i >= 1; i--) {
  220.     user = k[i]
  221.     LogTot = 0
  222.     line = ""
  223.     for ( tty = TTYList[T = 1]; T <= NumTTYs; tty = TTYList[++T] )
  224.         if (bytty_logins[tty,user]) {
  225.         line = line sprintf("%" W "d",bytty_logins[tty,user])
  226.         LogTot += bytty_logins[tty,user]
  227.         }
  228.         else
  229.         line = line sprintf("%" W "s","-")
  230.     printf Format "%s  %s\n",user,LogTot,Min2HM(min[user]),
  231.     Min2HM(TimePerLogin[user]),line,LastLogins[user]
  232.     nus++
  233.     TotalLogins += LogTot
  234.     TotMinutes += min[user]
  235.     }
  236. }
  237.  
  238. function PrintTTYtots(Format,TTYList,W,TotalLogins,nus,TotMinutes,NumTTYs,
  239. tty_logins,tty_min,  Blank,Bar,Title,tty,T,i) {
  240.     Blank = sprintf(Format,"","","","")
  241.     Bar = Blank
  242.     gsub(" ","-",Bar)
  243.     printf Bar
  244.     for ( tty = TTYList[T = 1]; T <= NumTTYs; tty = TTYList[++T] )
  245.     for (i = 1; i <= W; i++)
  246.         printf "-"
  247.     if (TotalLogins > 0)
  248.     printf "\n" Format,nus,TotalLogins,Min2HM(TotMinutes),
  249.     Min2HM(TotMinutes/TotalLogins)
  250.     for ( tty = TTYList[T = 1]; T <= NumTTYs; tty = TTYList[++T] )
  251.     printf "%" W-1 "d ",tty_logins[tty]
  252.     printf "\n"
  253.     Title = "Total hours TTY used:"
  254.     printf Title substr(Blank,length(Title) + 1)
  255.     for ( tty = TTYList[T = 1]; T <= NumTTYs; tty = TTYList[++T] )
  256.     printf "%" W-1 "d ",(tty_min[tty] + 59) / 60
  257.     printf "\n"
  258. }
  259.  
  260. ### Start of ProcArgs library
  261. # @(#) ProcArgs 1.11 96/12/08
  262. # 92/02/29 john h. dubois iii (john@armory.com)
  263. # 93/07/18 Added "#" arg type
  264. # 93/09/26 Do not count -h against MinArgs
  265. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  266. #          Removed meaning of "+" or "-" by itself.
  267. # 94/03/08 Added & option and *()< option types.
  268. # 94/04/02 Added NoRCopt to Opts()
  269. # 94/06/11 Mark numeric variables as such.
  270. # 94/07/08 Opts(): Do not require any args if h option is given.
  271. # 95/01/22 Record options given more than once.  Record option num in argv.
  272. # 95/06/08 Added ExclusiveOptions().
  273. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  274. #          Expand $VARNAME at the start of its filenames.
  275. #          Let varname=0 and -option- turn off an option.
  276. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  277. #          of the vars should be searched for in the environment.
  278. #          Check for duplicate rcfiles.
  279. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  280. #          now return various negatives values on error, not just -1, and
  281. #          Opts() may set Err to various positive values, not just 1.
  282. #          Added AllowUnrecOpt.
  283. # 96/05/23 Check type given for & option
  284. # 96/06/15 Re-port to awk
  285. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  286. #          used by other functions.
  287. # 96/10/15 Added OptChars
  288. # 96/11/01 Added exOpts arg to Opts()
  289. # 96/11/16 Added ; type
  290. # 96/12/08 Added Opt2Set() & Opt2Sets()
  291. # 96/12/27 Added CmdLineOpt()
  292.  
  293. # optlist is a string which contains all of the possible command line options.
  294. # A character followed by certain characters indicates that the option takes
  295. # an argument, with type as follows:
  296. # :    String argument
  297. # ;    Non-empty string argument
  298. # *    Floating point argument
  299. # (    Non-negative floating point argument
  300. # )    Positive floating point argument
  301. # #    Integer argument
  302. # <    Non-negative integer argument
  303. # >    Positive integer argument
  304. # The only difference the type of argument makes is in the runtime argument
  305. # error checking that is done.
  306.  
  307. # The & option is a special case used to get numeric options without the
  308. # user having to give an option character.  It is shorthand for [-+.0-9].
  309. # If & is included in optlist and an option string that begins with one of
  310. # these characters is seen, the value given to "&" will include the first
  311. # char of the option.  & must be followed by a type character other than ":"
  312. # or ";".
  313. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  314.  
  315. # Strings in argv[] which begin with "-" or "+" are taken to be
  316. # strings of options, except that a string which consists solely of "-"
  317. # or "+" is taken to be a non-option string; like other non-option strings,
  318. # it stops the scanning of argv and is left in argv[].
  319. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  320. # If an option takes an argument, the argument may either immediately
  321. # follow it or be given separately.
  322. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  323. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  324. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  325. # this feature had a flaw that caused problems in some cases.  See the OptChars
  326. # parameter to explicitly set the option-specifier characters.
  327.  
  328. # If an option that does not take an argument is given,
  329. # an index with its name is created in Options and its value is set to the
  330. # number of times it occurs in argv[].
  331.  
  332. # If an option that does take an argument is given, an index with its name is
  333. # created in Options and its value is set to the value of the argument given
  334. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  335. # If an option that takes an argument is given more than once,
  336. # Options[option-name,"count"] is incremented, and the value is assigned to
  337. # the index (option-name,instance) where instance is 2 for the second occurance
  338. # of the option, etc.
  339. # In other words, the first time an option with a value is encountered, the
  340. # value is assigned to an index consisting only of its name; for any further
  341. # occurances of the option, the value index has an extra (count) dimension.
  342.  
  343. # The sequence number for each option found in argv[] is stored in
  344. # Options[option-name,"num",instance], where instance is 1 for the first
  345. # occurance of the option, etc.  The sequence number starts at 1 and is
  346. # incremented for each option, both those that have a value and those that
  347. # do not.  Options set from a config file have a value of 0 assigned to this.
  348.  
  349. # Options and their arguments are deleted from argv.
  350. # Note that this means that there may be gaps left in the indices of argv[].
  351. # If compress is nonzero, argv[] is packed by moving its elements so that
  352. # they have contiguous integer indices starting with 0.
  353. # Option processing will stop with the first unrecognized option, just as
  354. # though -- was given except that unlike -- the unrecognized option will not be
  355. # removed from ARGV[].  Normally, an error value is returned in this case.
  356. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  357. # be found, so the number of remaining arguments is returned instead.
  358. # If OptChars is not a null string, it is the set of characters that indicate
  359. # that an argument is an option string if the string begins with one of the
  360. # characters.  A string consisting solely of two of the same option-indicator
  361. # characters stops the scanning of argv[].  The default is "-+".
  362. # argv[0] is not examined.
  363. # The number of arguments left in argc is returned.
  364. # If an error occurs, the global string OptErr is set to an error message
  365. # and a negative value is returned.
  366. # Current error values:
  367. # -1: option that required an argument did not get it.
  368. # -2: argument of incorrect type supplied for an option.
  369. # -3: unrecognized (invalid) option.
  370. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  371. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  372. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  373. {
  374. # ArgNum is the index of the argument being processed.
  375. # ArgsLeft is the number of arguments left in argv.
  376. # Arg is the argument being processed.
  377. # ArgLen is the length of the argument being processed.
  378. # ArgInd is the position of the character in Arg being processed.
  379. # Option is the character in Arg being processed.
  380. # Pos is the position in OptList of the option being processed.
  381. # NumOpt is true if a numeric option may be given.
  382.     ArgsLeft = argc
  383.     NumOpt = index(OptList,"&")
  384.     OptionNum = 0
  385.     if (OptChars == "")
  386.     OptChars = "-+"
  387.     while (OptChars != "") {
  388.     c = substr(OptChars,1,1)
  389.     OptChars = substr(OptChars,2)
  390.     OptCharSet[c]
  391.     OptTerm[c c]
  392.     }
  393.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  394.     Arg = argv[ArgNum]
  395.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  396.         break    # Not an option; quit
  397.     if (Arg in OptTerm) {
  398.         delete argv[ArgNum]
  399.         ArgsLeft--
  400.         break
  401.     }
  402.     ArgLen = length(Arg)
  403.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  404.         Option = substr(Arg,ArgInd,1)
  405.         if (NumOpt && Option ~ /[-+.0-9]/) {
  406.         # If this option is a numeric option, make its flag be & and
  407.         # its option string flag position be the position of & in
  408.         # the option string.
  409.         Option = "&"
  410.         Pos = NumOpt
  411.         # Prefix Arg with a char so that ArgInd will point to the
  412.         # first char of the numeric option.
  413.         Arg = "&" Arg
  414.         ArgLen++
  415.         }
  416.         # Find position of flag in option string, to get its type (if any).
  417.         # Disallow & as literal flag.
  418.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  419.         if (AllowUnrecOpt) {
  420.             Escape = 1
  421.             break
  422.         }
  423.         else {
  424.             OptErr = "Invalid option: " specGiven Option
  425.             return -3
  426.         }
  427.         }
  428.  
  429.         # Find what the value of the option will be if it takes one.
  430.         # NeedNextOpt is true if the option specifier is the last char of
  431.         # this arg, which means that if the option requires a value it is
  432.         # the next arg.
  433.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  434.         if (GotValue = ArgNum + 1 < argc)
  435.             Value = argv[ArgNum+1]
  436.         }
  437.         else {    # Value is included with option
  438.         Value = substr(Arg,ArgInd + 1)
  439.         GotValue = 1
  440.         }
  441.  
  442.         if (HadValue = AssignVal(Option,Value,Options,
  443.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  444.         specGiven)) {
  445.         if (HadValue < 0)    # error occured
  446.             return HadValue
  447.         if (HadValue == 2)
  448.             ArgInd++    # Account for the single-char value we used.
  449.         else {
  450.             if (NeedNextOpt) {    # option took next arg as value
  451.             delete argv[++ArgNum]
  452.             ArgsLeft--
  453.             }
  454.             break    # This option has been used up
  455.         }
  456.         }
  457.     }
  458.     if (Escape)
  459.         break
  460.     # Do not delete arg until after processing of it, so that if it is not
  461.     # recognized it can be left in ARGV[].
  462.     delete argv[ArgNum]
  463.     ArgsLeft--
  464.     }
  465.     if (compress != 0) {
  466.     dest = 1
  467.     src = argc - ArgsLeft + 1
  468.     for (count = ArgsLeft - 1; count; count--) {
  469.         ARGV[dest] = ARGV[src]
  470.         dest++
  471.         src++
  472.     }
  473.     }
  474.     return ArgsLeft
  475. }
  476.  
  477. # Assignment to values in Options[] occurs only in this function.
  478. # Option: Option specifier character.
  479. # Value: Value to be assigned to option, if it takes a value.
  480. # Options[]: Options array to return values in.
  481. # ArgType: Argument type specifier character.
  482. # GotValue: Whether any value is available to be assigned to this option.
  483. # Name: Name of option being processed.
  484. # OptionNum: Number of this option (starting with 1) if set in argv[],
  485. #     or 0 if it was given in a config file or in the environment.
  486. # SingleOpt: true if the value (if any) that is available for this option was
  487. #     given as part of the same command line arg as the option.  Used only for
  488. #     options from the command line.
  489. # specGiven is the option specifier character use, if any (e.g. - or +),
  490. # for use in error messages.
  491. # Global variables: OptErr
  492. # Return value: negative value on error, 0 if option did not require an
  493. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  494. # the arg.
  495. # Current error values:
  496. # -1: Option that required an argument did not get it.
  497. # -2: Value of incorrect type supplied for option.
  498. # -3: Bad type given for option &
  499. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  500. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  501.     # If option takes a value...    [
  502.     NumTypes = "*()#<>]"
  503.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  504.     OptErr = "Bad type given for & option"
  505.     return -3
  506.     }
  507.  
  508.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  509.     if (!GotValue) {
  510.         if (Name != "")
  511.         OptErr = "Variable requires a value -- " Name
  512.         else
  513.         OptErr = "option requires an argument -- " Option
  514.         return -1
  515.     }
  516.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  517.         OptErr = Err
  518.         return -2
  519.     }
  520.     # Mark this as a numeric variable; will be propogated to Options[] val.
  521.     if (ArgType != ":" && ArgType != ";")
  522.         Value += 0
  523.     if ((Instance = ++Options[Option,"count"]) > 1)
  524.         Options[Option,Instance] = Value
  525.     else
  526.         Options[Option] = Value
  527.     }
  528.     # If this is an environ or rcfile assignment & it was given a value...
  529.     else if (!OptionNum && Value != "") {
  530.     UsedValue = 1
  531.     # If the value is "0" or "-" and this is the first instance of it,
  532.     # do not set Options[Option]; this allows an assignment in an rcfile to
  533.     # turn off an option (for the simple "Option in Options" test) in such
  534.     # a way that it cannot be turned on in a later file.
  535.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  536.         Instance = 1
  537.     else
  538.         Instance = ++Options[Option]
  539.     # Save the value even though this is a flag
  540.     Options[Option,Instance] = Value
  541.     }
  542.     # If this is a command line flag and has a - following it in the same arg,
  543.     # it is being turned off.
  544.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  545.     UsedValue = 2
  546.     if (Option in Options)
  547.         Instance = ++Options[Option]
  548.     else
  549.         Instance = 1
  550.     Options[Option,Instance]
  551.     }
  552.     # If this is a flag assignment without a value, increment the count for the
  553.     # flag unless it was turned off.  The indicator for a flag being turned off
  554.     # is that the flag index has not been set in Options[] but it has an
  555.     # instance count.
  556.     else if (Option in Options || !((Option,1) in Options))
  557.     # Increment number of times this flag seen; will inc null value to 1
  558.     Instance = ++Options[Option]
  559.     Options[Option,"num",Instance] = OptionNum
  560.     return UsedValue
  561. }
  562.  
  563. # Option is the option letter
  564. # Value is the value being assigned
  565. # Name is the var name of the option, if any
  566. # ArgType is one of:
  567. # :    String argument
  568. # ;    Non-null string argument
  569. # *    Floating point argument
  570. # (    Non-negative floating point argument
  571. # )    Positive floating point argument
  572. # #    Integer argument
  573. # <    Non-negative integer argument
  574. # >    Positive integer argument
  575. # specGiven is the option specifier character use, if any (e.g. - or +),
  576. # for use in error messages.
  577. # Returns null on success, err string on error
  578. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  579.     if (ArgType == ":")
  580.     return ""
  581.     if (ArgType == ";") {
  582.     if (Value == "")
  583.         Err = "must be a non-empty string"
  584.     }
  585.     # A number begins with optional + or -, and is followed by a string of
  586.     # digits or a decimal with digits before it, after it, or both
  587.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  588.     Err = "must be a number"
  589.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  590.     Err = "may not include a fraction"
  591.     else if (ArgType ~ "[()<>]" && Value < 0)
  592.     Err = "may not be negative"
  593.     # (
  594.     else if (ArgType ~ "[)>]" && Value == 0)
  595.     Err = "must be a positive number"
  596.     if (Err != "") {
  597.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  598.     if (Name != "")
  599.         return ErrStr "variable " substr(Name,1,1) " " Err
  600.     else {
  601.         if (Option == "&")
  602.         Option = Value
  603.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  604.     }
  605.     }
  606.     else
  607.     return ""
  608. }
  609.  
  610. # Note: only the above functions are needed by ProcArgs.
  611. # The rest of these functions call ProcArgs() and also do other
  612. # option-processing stuff.
  613.  
  614. # Opts: Process command line arguments.
  615. # Opts processes command line arguments using ProcArgs()
  616. # and checks for errors.  If an error occurs, a message is printed
  617. # and the program is exited.
  618. #
  619. # Input variables:
  620. # Name is the name of the program, for error messages.
  621. # Usage is a usage message, for error messages.
  622. # OptList the option description string, as used by ProcArgs().
  623. # MinArgs is the minimum number of non-option arguments that this
  624. # program should have, non including ARGV[0] and +h.
  625. # If the program does not require any non-option arguments,
  626. # MinArgs should be omitted or given as 0.
  627. # rcFiles, if given, is a colon-seprated list of filenames to read for
  628. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  629. # by the value of the environment variable HOME.  If a filename begins with
  630. # $, the part from the character after the $ up until (but not including)
  631. # the first character not in [a-zA-Z0-9_] will be searched for in the
  632. # environment; if found its value will be substituted, if not the filename will
  633. # be discarded.
  634. # rcfiles are read in the order given.
  635. # Values given in them will not override values given on the command line,
  636. # and values given in later files will not override those set in earlier
  637. # files, because AssignVal() will store each with a different instance index.
  638. # The first instance of each variable, either on the command line or in an
  639. # rcfile, will be stored with no instance index, and this is the value
  640. # normally used by programs that call this function.
  641. # VarNames is a comma-separated list of variable names to map to options,
  642. # in the same order as the options are given in OptList.
  643. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  644. # searched for in the environment.  If set to -1, all values will be searched
  645. # for in the environment.  Values given in the environment will override
  646. # those given in the rcfiles but not those given on the command line.
  647. # NoRCopt, if given, is an additional letter option that if given on the
  648. # command line prevents the rcfiles from being read.
  649. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  650. # ExclusiveOptions() for a description of exOpts.
  651. # Special options:
  652. # If x is made an option and is given, some debugging info is output.
  653. # h is assumed to be the help option.
  654.  
  655. # Global variables:
  656. # The command line arguments are taken from ARGV[].
  657. # The arguments that are option specifiers and values are removed from
  658. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  659. # The number of elements in ARGV[] should be in ARGC.
  660. # After processing, ARGC is set to the number of elements left in ARGV[].
  661. # The option values are put in Options[].
  662. # On error, Err is set to a positive integer value so it can be checked for in
  663. # an END block.
  664. # Return value: The number of elements left in ARGV is returned.
  665. # Must keep OptErr global since it may be set by InitOpts().
  666. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  667. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  668.     if (MinArgs == "")
  669.     MinArgs = 0
  670.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  671.     optChars)
  672.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  673.     if (ArgsLeft >= 0) {
  674.         OptErr = "Not enough arguments"
  675.         Err = 4
  676.     }
  677.     else
  678.         Err = -ArgsLeft
  679.     printf "%s: %s.\nUse -h for help.\n%s\n",
  680.     Name,OptErr,Usage > "/dev/stderr"
  681.     exit 1
  682.     }
  683.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  684.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  685.     {
  686.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  687.     Err = -e
  688.     exit 1
  689.     }
  690.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  691.     {
  692.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  693.     Err = 1
  694.     exit 1
  695.     }
  696.     return ArgsLeft
  697. }
  698.  
  699. # ReadConfFile(): Read a file containing var/value assignments, in the form
  700. # <variable-name><assignment-char><value>.
  701. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  702. # line and whitespace between the variable name and the assignment character) 
  703. # is stripped.  Lines that do not contain an assignment operator or which
  704. # contain a null variable name are ignored, other than possibly being noted in
  705. # the return value.  If more than one assignment is made to a variable, the
  706. # first assignment is used.
  707. # Input variables:
  708. # File is the file to read.
  709. # Comment is the line-comment character.  If it is found as the first non-
  710. #     whitespace character on a line, the line is ignored.
  711. # Assign is the assignment string.  The first instance of Assign on a line
  712. #     separates the variable name from its value.
  713. # If StripWhite is true, whitespace around the value (whitespace between the
  714. #     assignment char and trailing whitespace on the line) is stripped.
  715. # VarPat is a pattern that variable names must match.  
  716. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  717. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  718. #     a line; no assignment operator is needed.  These variables are set in
  719. #     the output array with a null value.  Lines containing nothing but
  720. #     whitespace are still ignored.
  721. # Output variables:
  722. # Values[] contains the assignments, with the indexes being the variable names
  723. #     and the values being the assigned values.
  724. # Lines[] contains the line number that each variable occured on.  A flag set
  725. #     is record by giving it an index in Lines[] but not in Values[].
  726. # Return value:
  727. # If any errors occur, a string consisting of descriptions of the errors
  728. # separated by newlines is returned.  In no case will the string start with a
  729. # numeric value.  If no errors occur,  the number of lines read is returned.
  730. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  731. FlagsOK,
  732. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  733.     if (Comment != "")
  734.     Comment = "^" Comment
  735.     AssignLen = length(Assign)
  736.     if (VarPat == "")
  737.     VarPat = "."    # null varname not allowed
  738.     while ((Status = (getline Line < File)) == 1) {
  739.     LineNum++
  740.     sub("^[ \t]+","",Line)
  741.     if (Line == "")        # blank line
  742.         continue
  743.     if (Comment != "" && Line ~ Comment)
  744.         continue
  745.     if (Pos = index(Line,Assign)) {
  746.         Var = substr(Line,1,Pos-1)
  747.         Val = substr(Line,Pos+AssignLen)
  748.         if (StripWhite) {
  749.         sub("^[ \t]+","",Val)
  750.         sub("[ \t]+$","",Val)
  751.         }
  752.     }
  753.     else {
  754.         Var = Line    # If no value, var is entire line
  755.         Val = ""
  756.     }
  757.     if (!FlagsOK && Val == "") {
  758.         Errs = Errs \
  759.         sprintf("\nBad assignment on line %d of file %s: %s",
  760.         LineNum,File,Line)
  761.         continue
  762.     }
  763.     sub("[ \t]+$","",Var)
  764.     if (Var !~ VarPat) {
  765.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  766.         LineNum,File,Var)
  767.         continue
  768.     }
  769.     if (!(Var in Lines)) {
  770.         Lines[Var] = LineNum
  771.         if (Pos)
  772.         Values[Var] = Val
  773.     }
  774.     }
  775.     if (Status)
  776.     Errs = Errs "\nCould not read file " File
  777.     close(File)
  778.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  779. }
  780.  
  781. # Variables:
  782. # Data is stored in Options[].
  783. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  784. # Global vars:
  785. # Sets OptErr.  Uses ENVIRON[].
  786. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  787. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  788. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  789. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  790.     split("",filesRead,"")    # make awk know this is an array
  791.     NumVars = split(VarNames,Vars,",")
  792.     TypesInd = Ret = 0
  793.     if (EnvSearch == -1)
  794.     EnvSearch = NumVars
  795.     for (i = 1; i <= NumVars; i++) {
  796.     Var = Vars[i]
  797.     CharOpt = substr(OptList,++TypesInd,1)
  798.     if (CharOpt ~ "^[:;*()#<>&]$")
  799.         CharOpt = substr(OptList,++TypesInd,1)
  800.     Map[Var] = CharOpt
  801.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  802.     # Do not overwrite entries from environment
  803.     if (i <= EnvSearch && Var in ENVIRON &&
  804.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  805.         return Err
  806.     }
  807.  
  808.     numrcFiles = split(rcFiles,fNames,":")
  809.     for (i = 1; i <= numrcFiles; i++) {
  810.     rcFile = fNames[i]
  811.     if (rcFile ~ "^~/")
  812.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  813.     else if (rcFile ~ /^\$/) {
  814.         rcFile = substr(rcFile,2)
  815.         match(rcFile,"^[a-zA-Z0-9_]*")
  816.         envvar = substr(rcFile,1,RLENGTH)
  817.         if (envvar in ENVIRON)
  818.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  819.         else
  820.         continue
  821.     }
  822.     if (rcFile in filesRead)
  823.         continue
  824.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  825.     # may be the same
  826.     filesRead[rcFile]
  827.     if ("x" in Options)
  828.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  829.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  830.     if (retStr > 0)
  831.         READ_RCFILE = 1
  832.     else if (ret != "") {
  833.         OptErr = retStr
  834.         Ret = -1
  835.     }
  836.     for (Var in Lines)
  837.         if (Var in Map) {
  838.         if ((Err = AssignVal(Map[Var],
  839.         Var in Values ? Values[Var] : "",Options,Types[Var],
  840.         Var in Values,Var,0)) < 0)
  841.             return Err
  842.         }
  843.         else {
  844.         OptErr = sprintf(\
  845.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  846.         Lines[Var],rcFile)
  847.         Ret = -1
  848.         }
  849.     }
  850.  
  851.     if ("x" in Options)
  852.     for (Var in Map)
  853.         if (Map[Var] in Options)
  854.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  855.         "/dev/stderr"
  856.         else
  857.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  858.     return Ret
  859. }
  860.  
  861. # OptSets is a semicolon-separated list of sets of option sets.
  862. # Within a list of option sets, the option sets are separated by commas.  For
  863. # each set of sets, if any option in one of the sets is in Options[] AND any
  864. # option in one of the other sets is in Options[], an error string is returned.
  865. # If no conflicts are found, nothing is returned.
  866. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  867. # the exclusions presented by the first set of sets (ab,def,g) if:
  868. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  869. # (a or b is in Options[]) AND (g is in Options) OR
  870. # (d, e, or f is in Options[]) AND (g is in Options)
  871. # An error will be returned due to the exclusions presented by the second set
  872. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  873. # todo: make options given on command line unset options given in config file
  874. # todo: that they conflict with.
  875. function ExclusiveOptions(OptSets,Options,
  876. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  877. SetNum,OSetNum) {
  878.     NumSetSets = split(OptSets,SetSets,";")
  879.     # For each set of sets...
  880.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  881.     # NumSets is the number of sets in this set of sets.
  882.     NumSets = split(SetSets[SetSet],Sets,",")
  883.     # For each set in a set of sets except the last...
  884.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  885.         s1 = Sets[SetNum]
  886.         L1 = length(s1)
  887.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  888.         # If any of the options in this set was given, check whether
  889.         # any of the options in the other sets was given.  Only check
  890.         # later sets since earlier sets will have already been checked
  891.         # against this set.
  892.         if ((c1 = substr(s1,Pos1,1)) in Options)
  893.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  894.             s2 = Sets[OSetNum]
  895.             L2 = length(s2)
  896.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  897.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  898.                 ErrStr = ErrStr "\n"\
  899.                 sprintf("Cannot give both %s and %s options.",
  900.                 c1,c2)
  901.             }
  902.     }
  903.     }
  904.     if (ErrStr != "")
  905.     return substr(ErrStr,2)
  906.     return ""
  907. }
  908.  
  909. # The value of each instance of option Opt that occurs in Options[] is made an
  910. # index of Set[].
  911. # The return value is the number of instances of Opt in Options.
  912. function Opt2Set(Options,Opt,Set,  count) {
  913.     if (!(Opt in Options))
  914.     return 0
  915.     Set[Options[Opt]]
  916.     count = Options[Opt,"count"]
  917.     for (; count > 1; count--)
  918.     Set[Options[Opt,count]]
  919.     return count
  920. }
  921.  
  922. # The value of each instance of option Opt that occurs in Options[] that
  923. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  924. # Other values are made indexes of Set[].
  925. # The return value is the number of instances of Opt in Options.
  926. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  927.     ret = Opt2Set(Options,Opt,aSet)
  928.     for (value in aSet)
  929.     if (substr(value,1,1) == "!")
  930.         nSet[substr(value,2)]
  931.     else
  932.         Set[value]
  933.     return ret
  934. }
  935.  
  936. # Returns true if option Opt was given on the command line.
  937. function CmdLineOpt(Options,Opt,  i) {
  938.     for (i = 1; (Opt,"num",i) in Options; i++)
  939.     if (Options[Opt,"num",i] != 0)
  940.         return 1
  941.     return 0
  942. }
  943. ### End of ProcArgs library
  944.  
  945. function Intersection(A,B,Inter,  Elem) {
  946.     for (Elem in A)
  947.     if (Elem in B)
  948.         Inter[Elem]
  949. }
  950.  
  951. function Union(A,B,Both,  Elem) {
  952.     for (Elem in A)
  953.     Both[Elem]
  954.     for (Elem in B)
  955.     Both[Elem]
  956. }
  957.  
  958. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  959. function SubtractSet(Minuend,Subtrahend,  Elem) {
  960.     for (Elem in Subtrahend)
  961.     delete Minuend[Elem]
  962. }
  963.  
  964. function CopySet(From,To,  Elem) {
  965.     for (Elem in From)
  966.     To[Elem]
  967. }
  968.  
  969. # Returns 1 if Set is empty, 0 if not.
  970. function IsEmpty(Set,  i) {
  971.     for (i in Set)
  972.     return 0
  973.     return 1
  974. }
  975.  
  976. # MakeSet: make a set from a list.
  977. # An index with the name of each element of the list
  978. # is created in the given array.
  979. # Input variables: 
  980. # Elements is a string containing the list of elements.
  981. # Sep is the character that separates the elements of the list.
  982. # Output variables:
  983. # Set is the array.
  984. # Return value: the number of elements added to the set.
  985. function MakeSet(Set,Elements,Sep,  Num,Names) {
  986.     Num = split(Elements,Names,Sep)
  987.     for (; Num; Num--)
  988.     Set[Names[Num]]
  989.     return Num
  990. }
  991. # Returns the number of elements in set Set
  992. function NumElem(Set,  elem,Num) {
  993.     for (elem in Set)
  994.     Num++
  995.     return Num
  996. }
  997.  
  998. ### Begin qsort routines
  999.  
  1000. # Arr[] is an array of values with arbitrary indices.
  1001. # k[] is returned with numeric indices 1..n.
  1002. # The values in k[] are the indices of Arr[],
  1003. # ordered so that if Arr[] is stepped through
  1004. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1005. # through in order of the values of its elements.
  1006. # The return value is the number of elements in the arrays (n).
  1007. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1008.     ElNum = 0
  1009.     for (ArrInd in Arr)
  1010.     k[++ElNum] = ArrInd
  1011.     qsortSegment(Arr,k,1,ElNum)
  1012.     return ElNum
  1013. }
  1014.  
  1015. # Sort a segment of an array.
  1016. # Arr[] contains data with arbitrary indices.
  1017. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1018. # This function sorts the elements of arr that are pointed to by
  1019. # k[start..end], swapping the values of elements of k[] so that
  1020. # when this function returns arr[k[start..end]] will be in order.
  1021. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1022.     # handle two-element case explicitly for a tiny speedup
  1023.     if ((end - start) == 1) {
  1024.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1025.         k[start] = tmpe
  1026.         k[end] = tmps
  1027.     }
  1028.     return
  1029.     }
  1030.     # Make sure comparisons act on these as numbers
  1031.     left = start+0
  1032.     right = end+0
  1033.     sepval = Arr[k[int((left + right) / 2)]]
  1034.     # Make every element <= sepval be to the left of every element > sepval
  1035.     while (left < right) {
  1036.     while (Arr[k[left]] < sepval)
  1037.         left++
  1038.     while (Arr[k[right]] > sepval)
  1039.         right--
  1040.     if (left < right) {
  1041.         tmp = k[left]
  1042.         k[left++] = k[right]
  1043.         k[right--] = tmp
  1044.     }
  1045.     }
  1046.     if (left == right)
  1047.     if (Arr[k[left]] < sepval)
  1048.         left++
  1049.     else
  1050.         right--
  1051.     if (start < right)
  1052.     qsortSegment(Arr,k,start,right)
  1053.     if (left < end)
  1054.     qsortSegment(Arr,k,left,end)
  1055. }
  1056.  
  1057. # Arr[] is an array of values with arbitrary indices.
  1058. # k[] is returned with numeric indices 1..n.
  1059. # The values in k are the indices of Arr[],
  1060. # ordered so that if Arr[] is stepped through
  1061. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1062. # through in order of the values of its indices.
  1063. # The return value is the number of elements in the arrays (n).
  1064. # If the indexes are numeric, Numeric should be true, so that they can be
  1065. # compared as such rather than as strings.  Numeric indexes do not have to be
  1066. # contiguous.
  1067. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1068.     ElNum = 0
  1069.     if (Numeric)
  1070.     # Indexes do not preserve numeric type, so must be forced
  1071.     for (ArrInd in Arr)
  1072.         k[++ElNum] = ArrInd+0
  1073.     else
  1074.     for (ArrInd in Arr)
  1075.         k[++ElNum] = ArrInd
  1076.     qsortNumIndByValue(k,1,ElNum)
  1077.     return ElNum
  1078. }
  1079.  
  1080. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1081. # by value.
  1082. # start and end are the starting and ending indexes of the range to be sorted.
  1083. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1084.     # handle two-element case explicitly for a tiny speedup
  1085.     if ((start - end) == 1) {
  1086.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1087.         Arr[start] = tmpe
  1088.         Arr[end] = tmps
  1089.     }
  1090.     return
  1091.     }
  1092.     left = start+0
  1093.     right = end+0
  1094.     sepval = Arr[int((left + right) / 2)]
  1095.     while (left < right) {
  1096.     while (Arr[left] < sepval)
  1097.         left++
  1098.     while (Arr[right] > sepval)
  1099.         right--
  1100.     if (left <= right) {
  1101.         tmp = Arr[left]
  1102.         Arr[left++] = Arr[right]
  1103.         Arr[right--] = tmp
  1104.     }
  1105.     }
  1106.     if (start < right)
  1107.     qsortNumIndByValue(Arr,start,right)
  1108.     if (left < end)
  1109.     qsortNumIndByValue(Arr,left,end)
  1110. }
  1111.  
  1112. ### End qsort routines
  1113.